import com.alexmerz.graphviz.objects.Node;

import java.util.*;
import java.util.stream.Collectors;

abstract class Subgraph {
  private String name = "";
  private String description = "";

  abstract String getKindString();

  Subgraph() {
  }

  Subgraph(String name, String description) {
    this.name = name;
    this.description = description;
  }

  public void setName(String name) {
    this.name = Optional.ofNullable(name).orElse("");
  }

  public void setDescription(String description) {
    this.description = Optional.ofNullable(description).orElse("");
  }

  public String getName() {
    return name;
  }

  public String getDescription() {
    return description;
  }

  abstract Subgraph kind();
}

public class Cluster extends Subgraph {
  static class Artefact extends Subgraph {
    @Override
    String getKindString() {
      return "ARTEFACT";
    }

    Artefact(String name, String description) {
      super(name, description);
    }

    @Override
    Subgraph kind() {
      return this;
    }

    static Artefact instance(Node nEnt) {
      return new Artefact(nEnt.getId().getId(), nEnt.getAttribute("description"));
    }
  }

  static class Furniture extends Subgraph {
    @Override
    String getKindString() {
      return "FURNITURE";
    }

    Furniture(String name, String description) {
      super(name, description);
    }

    @Override
    Subgraph kind() {
      return this;
    }

    static Furniture instance(Node nEnt) {
      return new Furniture(nEnt.getId().getId(), nEnt.getAttribute("description"));
    }
  }

  static class Character extends Subgraph {
    @Override
    String getKindString() {
      return "CHARACTER";
    }

    Character(String name, String description) {
      super(name, description);
    }

    @Override
    Subgraph kind() {
      return this;
    }

    static Character instance(Node nEnt) {
      return new Character(nEnt.getId().getId(), nEnt.getAttribute("description"));
    }
  }

  @Override
  String getKindString() {
    return "CLUSTER";
  }

  Cluster() {
    super();
  }

  @Override
  Subgraph kind() {
    return this;
  }

  private List<Artefact> artefacts = new ArrayList<>();
  private List<Furniture> furniture = new ArrayList<>();
  private List<Character> characters = new ArrayList<>();

  public void setArtefacts(List<Artefact> artefacts) {
    this.artefacts = artefacts;
  }

  public void mergeArtefacts(Artefact inputArtefacts) {
    var a = new ArrayList<>(getArtefacts());
    a.add(inputArtefacts);
    this.artefacts = a.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Subgraph::getName))), ArrayList::new));
  }

  public void setFurniture(List<Furniture> furniture) {
    this.furniture = furniture;
  }

  public void setCharacters(List<Character> characters) {
    this.characters = characters;
  }

  public boolean isCharactersEmpty() {
    return characters == null || characters.isEmpty();
  }

  public List<Artefact> getArtefacts() {
    return artefacts;
  }

  public void addArtefact(Artefact artefact) {
    var newArtefacts = new ArrayList<>(getArtefacts());
    newArtefacts.add(artefact);
    setArtefacts(newArtefacts);
  }

  public Optional<Artefact> getArtefactBy(String name) {
    return getArtefacts().stream().filter(a -> a.getName().equals(name)).findFirst();
  }

  public void removeArtefactBy(Artefact artefact) {
    var newArtefacts = new ArrayList<>(artefacts);
    newArtefacts.remove(artefact);
    artefacts = newArtefacts;
  }

  public void removeFurnitureBy(String name) {
    var furniture1 = getFurniture().stream().filter(f -> f.getName().equals(name)).findFirst();
    furniture1.ifPresent(this::removeFurnitureBy);
  }

  public void removeFurnitureBy(Furniture f) {
    var newFurniture = new ArrayList<>(furniture);
    newFurniture.remove(f);
    furniture = newFurniture;
  }

  public void removeCharacterBy(String name) {
    var character = getCharacters().stream().filter(c -> c.getName().equals(name)).findFirst();
    character.ifPresent(this::removeCharacterBy);
  }

  public void removeCharacterBy(Character character) {
    if (!isCharactersEmpty()) {
      var newCharacters = new ArrayList<>(characters);
      newCharacters.remove(character);
      characters = newCharacters;
    }
  }

  public List<Furniture> getFurniture() {
    return furniture;
  }

  public List<Character> getCharacters() {
    return characters;
  }

  public Optional<Cluster.Furniture> getFurnitureBy(String name) {
    for (var f : getFurniture()) {
      if (f.getName().equals(name)) {
        return Optional.of(f);
      }
    }
    return Optional.empty();
  }

  public Optional<Cluster.Character> getCharacterBy(String name) {
    for (var f : getCharacters()) {
      if (f.getName().equals(name)) {
        return Optional.of(f);
      }
    }
    return Optional.empty();
  }

}
